/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.Utils;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Collections;
using Xceed.Silverlight.Controls.Utils;
using Xceed.Silverlight.Compatibility;

namespace Xceed.Silverlight.Controls
{
  [TemplateVisualState( GroupName = "FilterStates", Name = "NoFilter" )]
  [TemplateVisualState( GroupName = "FilterStates", Name = "HasFilter" )]
  [TemplateVisualState( GroupName = "FilterStates", Name = "TooComplex" )]
  public class SearchTextBox : WatermarkTextBox, IFilterExpressionProvider, IWeakEventListener
  {
    #region Constructors

    static SearchTextBox()
    {
      char[] defaultSearchValueDelimiters = new char[]{ ' ', ',' };
      char[] calculatedSearchValueDelimiters = defaultSearchValueDelimiters;

      string textInfoListSeparator = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;

      if( textInfoListSeparator.Length == 1 )
      {
        char textInfoListSeparatorChar = textInfoListSeparator[ 0 ];

        bool addTextInfoListSeparatorChar = true;

        for( int i = 0; i < defaultSearchValueDelimiters.Length; i++ )
        {
          char delimiter = defaultSearchValueDelimiters[ i ];

          if( delimiter == textInfoListSeparatorChar )
          {
            addTextInfoListSeparatorChar = false;
            break;
          }
        }

        if( addTextInfoListSeparatorChar )
        {
          calculatedSearchValueDelimiters = new char[ defaultSearchValueDelimiters.Length + 1 ];
          defaultSearchValueDelimiters.CopyTo( calculatedSearchValueDelimiters, 0 );
          calculatedSearchValueDelimiters[ calculatedSearchValueDelimiters.Length - 1 ] = textInfoListSeparatorChar;
        }
      }

      SearchTextBox.SearchValueDelimitersProperty = DependencyProperty.Register( "SearchValueDelimiters",
        typeof( char[] ),
        typeof( SearchTextBox ),
        new PropertyMetadata( calculatedSearchValueDelimiters ) );
    }

    public SearchTextBox()
    {
      this.DefaultStyleKey = typeof( SearchTextBox );
      this.IsEnabled = false;
    }

    #endregion

    #region ClearFilterCommand Property

    public ICommand ClearFilterCommand
    {
      get
      {
        if( m_clearFilterCommand == null )
        {
          m_clearFilterCommand = new Command( new Action<object>( this.ClearFilterExcecute ),
            new Func<object, bool>( this.ClearFilterCanExecute ) );
        }

        return m_clearFilterCommand;
      }
    }

    protected virtual bool ClearFilterCanExecute( object parameter )
    {
      return this.FilterExpression != null;
    }

    protected virtual void ClearFilterExcecute( object parameter )
    {
      this.ClearFilterExpression();
    }

    private Command m_clearFilterCommand;

    #endregion

    #region FilterExpression Property

    public static readonly DependencyProperty FilterExpressionProperty = DependencyProperty.Register(
      "FilterExpression",
      typeof( FilterExpressionBase ),
      typeof( SearchTextBox ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( SearchTextBox.OnFilterExpressionChanged ) ) );

    public FilterExpressionBase FilterExpression
    {
      get
      {
        return ( FilterExpressionBase )this.GetValue( SearchTextBox.FilterExpressionProperty );
      }
      set
      {
        this.SetValue( SearchTextBox.FilterExpressionProperty, value );
      }
    }

    private static void OnFilterExpressionChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      SearchTextBox searchTextBox = sender as SearchTextBox;

      if( searchTextBox == null )
        return;

      searchTextBox.OnFilterExpressionChanged( e.OldValue as FilterExpressionBase, e.NewValue as FilterExpressionBase );
    }

    internal void OnFilterExpressionChanged( FilterExpressionBase oldValue, FilterExpressionBase newValue )
    {
      if( m_explicitlyUpdatingFilterExpression )
        return;

      if( m_clearingFilterExpression )
        return;

      if( newValue == null )
      {
        this.ClearFilterExpression();
      }

      this.ChangeVisualState();
      this.RaiseClearFilterCanExecute();
    }

    #endregion

    #region FilteredFieldNames Property

    public static readonly DependencyProperty FilteredFieldNamesProperty = DependencyProperty.RegisterAttached(
     "FilteredFieldNames",
     typeof( FieldNameList),
     typeof( SearchTextBox ),
     new PropertyMetadata(
       null,
       new PropertyChangedCallback( SearchTextBox.OnFilteredFieldNamesChanged ) ) );

    public static FieldNameList GetFilteredFieldNames( DependencyObject obj )
    {
      return ( FieldNameList )obj.GetValue( SearchTextBox.FilteredFieldNamesProperty );
    }

    public static void SetFilteredFieldNames( DependencyObject obj, FieldNameList value )
    {
      obj.SetValue( SearchTextBox.FilteredFieldNamesProperty, value );
    }

    public FieldNameList FilteredFieldNames
    {
      get
      {
        return ( FieldNameList )this.GetValue( SearchTextBox.FilteredFieldNamesProperty );
      }
      set
      {
        this.SetValue( SearchTextBox.FilteredFieldNamesProperty, value );
      }
    }

    private static void OnFilteredFieldNamesChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      SearchTextBox searchTextBox = sender as SearchTextBox;

      if( searchTextBox == null )
        return;

      FieldNameList oldFilteredFieldNames = e.OldValue as FieldNameList;
      FieldNameList newFilteredFieldNames = e.NewValue as FieldNameList;

      if( oldFilteredFieldNames != null )
      {
        NotifyCollectionChangedManager.RemoveListener( oldFilteredFieldNames, searchTextBox );
      }

      if( newFilteredFieldNames != null )
      {
        NotifyCollectionChangedManager.AddListener( newFilteredFieldNames, searchTextBox );
      }

      searchTextBox.OnFilteredFieldNamesCollectionChanged( newFilteredFieldNames, new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ) );
    }

    private void OnFilteredFieldNamesCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
#if !WINDOWS_PHONE
      if( !this.IsInitialized )
        return;
#endif

      this.UpdateCachedFilteredFieldNames();
      this.UpdateIsEnabled();
      this.UpdateFilterExpression();
    }

    #endregion

    #region SearchValueDelimiters Property

    public static readonly DependencyProperty SearchValueDelimitersProperty;

    public char[] SearchValueDelimiters
    {
      get
      {
        return ( char[] )this.GetValue( SearchTextBox.SearchValueDelimitersProperty );
      }
      set
      {
        this.SetValue( SearchTextBox.SearchValueDelimitersProperty, value );
      }
    }

    #endregion

    #region TooComplexText Property

    public static readonly DependencyProperty TooComplexTextProperty = DependencyProperty.RegisterAttached(
      "TooComplexText",
      typeof( string ),
      typeof( SearchTextBox ),
      new PropertyMetadata( "#######" ) );

    public static string GetTooComplexText( DependencyObject obj )
    {
      return ( string )obj.GetValue( SearchTextBox.TooComplexTextProperty );
    }

    public static void SetTooComplexText( DependencyObject obj, string value )
    {
      obj.SetValue( SearchTextBox.TooComplexTextProperty, value );
    }

    public string TooComplexText
    {
      get
      {
        return ( string )this.GetValue( SearchTextBox.TooComplexTextProperty );
      }
      set
      {
        this.SetValue( SearchTextBox.TooComplexTextProperty, value );
      }
    }

    #endregion

    #region Public Methods

    public void ClearFilterExpression()
    {
      try
      {
        m_clearingFilterExpression = true;
        this.ClearFilterExpressionCore();
      }
      finally
      {
        m_clearingFilterExpression = false;
      }

      m_lastLocallyCreatedFilterExpression = null;
      this.RaiseClearFilterCanExecute();
      this.ChangeVisualState();
    }

    #endregion

    #region Protected Methods

    protected override void EndInitCore()
    {
      this.UpdateCachedFilteredFieldNames();
      this.UpdateCachedSearchedValues();
      this.UpdateIsEnabled();
      this.UpdateFilterExpression();
      this.ChangeVisualState();
      this.RaiseClearFilterCanExecute();
    }

    protected override sealed void OnTextChanged( object sender, TextChangedEventArgs e )
    {
      base.OnTextChanged( sender, e );

#if !WINDOWS_PHONE
      if( !this.IsInitialized )
        return;
#endif

      this.UpdateCachedSearchedValues();
      this.UpdateFilterExpression();
      this.ChangeVisualState();
      this.RaiseClearFilterCanExecute();
    }

    protected internal override void ChangeVisualStateCore( bool useTransitions )
    {
      base.ChangeVisualStateCore( useTransitions );

      this.UpdateFilterStates( useTransitions );
    }

    protected virtual void ClearFilterExpressionCore()
    {
      this.Text = string.Empty;
      this.FilterExpression = null;
      m_lastLocallyCreatedFilterExpression = null;
    }

    protected FilterExpressionBase CreateFilterExpression()
    {
      m_lastLocallyCreatedFilterExpression = this.CreateFilterExpressionCore();
      return m_lastLocallyCreatedFilterExpression;
    }

    protected virtual FilterExpressionBase CreateFilterExpressionCore()
    {
      string searchedText = this.Text;

      if( m_cachedSearchedValues == null )
        return null;

      int searchedValuesCount = m_cachedSearchedValues.Length;

      if( searchedValuesCount == 0 )
        return null;

      if( m_cachedFilterFieldNames == null )
        return null;

      int fieldNamesCount = m_cachedFilterFieldNames.Length;

      if( fieldNamesCount == 0 )
        return null;


      if( fieldNamesCount > 1 )
      {
        FilterExpressionOperator filterExpressionOperator = new OrFilterExpression();

        foreach( string fieldName in m_cachedFilterFieldNames )
        {
          FilterExpressionBase fieldExpression = null;

          if( searchedValuesCount > 1 )
          {
            FilterExpressionOperator fieldFilterExpressionOperator = new OrFilterExpression();

            foreach( string searchedValue in m_cachedSearchedValues )
            {
              fieldFilterExpressionOperator.Add( new FilterExpression( fieldName.Trim(), FilterOperator.Contains, searchedValue ) );
            }

            fieldExpression = fieldFilterExpressionOperator;
          }
          else
          {
            fieldExpression = new FilterExpression( fieldName.Trim(), FilterOperator.Contains, m_cachedSearchedValues[ 0 ] );
          }

          filterExpressionOperator.Add( fieldExpression );
        }

        return filterExpressionOperator;
      }
      else
      {
        string fieldName = m_cachedFilterFieldNames[ 0 ].Trim();

        if( searchedValuesCount > 1 )
        {
          FilterExpressionOperator fieldFilterExpressionOperator = new OrFilterExpression();

          foreach( string searchedValue in m_cachedSearchedValues )
          {
            fieldFilterExpressionOperator.Add( new FilterExpression( fieldName, FilterOperator.Contains, searchedValue ) );
          }

          return fieldFilterExpressionOperator;
        }
        else
        {
          return new FilterExpression( fieldName, FilterOperator.Contains, m_cachedSearchedValues[ 0 ] );
        }
      }
    }

    #endregion

    #region Private Methods

    private void RaiseClearFilterCanExecute()
    {
      Command clearFilterCommand = this.ClearFilterCommand as Command;

      if( clearFilterCommand != null )
        clearFilterCommand.RaiseCanExecuteChanged();
    }

    private void UpdateCachedFilteredFieldNames()
    {
      FieldNameList filteredFieldNames = this.FilteredFieldNames;

      if( filteredFieldNames == null )
      {
        m_cachedFilterFieldNames = null;
        return;
      }

      m_cachedFilterFieldNames = new string[ filteredFieldNames.Count ];
      filteredFieldNames.CopyTo( m_cachedFilterFieldNames, 0 );
    }

    private void UpdateCachedSearchedValues()
    {
      m_cachedSearchedValues = null;

      string searchedText = this.Text;

      if( StringHelper.IsNullOrWhiteSpace( searchedText ) )
        return;

      List<string> searchValuesList = this.ProvideSearchValues( searchedText );
      m_cachedSearchedValues = searchValuesList.ToArray();

    }

    private List<string> ProvideSearchValues( string searchedText )
    {
      List<string> searchedValues = new List<string>();
      List<int> delimiterIndexes = new List<int>();

      int index = 0;

      // Build the list of delimiters index
      foreach( char car in searchedText )
      {
        // Look for quotes and keep position in string
        if( car == '"' )
          delimiterIndexes.Add( index );

        index++;
      }

      // No Delimiter: The search text contains only separated values.
      if( delimiterIndexes.Count == 0 )
      {
        this.AddSeparatedValuesToSearchedValues( searchedText, searchedValues );
        return searchedValues;
      }

      // Add the text that preceed the first quote in the 
      // separated values.
      if( delimiterIndexes[ 0 ] != 0 )
      {
        this.AddSeparatedValuesToSearchedValues( searchedText.Substring( 0, delimiterIndexes[ 0 ] ), searchedValues );
      }

      int delimiterIndexIndex = 0;

      foreach( int delimiterIndex in delimiterIndexes )
      {
        // We have an even index (open quote).
        if( delimiterIndexIndex % 2 == 0 )
        {
          // If we have a closing quote
          if( delimiterIndex != delimiterIndexes[ delimiterIndexes.Count - 1 ] )
          {
            // Compute the length of the string between the two quotes.
            int length = delimiterIndexes[ delimiterIndexIndex + 1 ] - ( delimiterIndex + 1 );

            // Add the string to te search values.
            string searchValue = searchedText.Substring( delimiterIndex + 1, length );
            if( ( !StringHelper.IsNullOrWhiteSpace( searchValue ) ) &&
                ( !string.IsNullOrEmpty( searchValue ) ) )
            {
              searchedValues.Add( searchValue );
            }
          }
          else
          {
            // There is no closing quote (error case)
            // Add each search values sperated by the separation delimiter
            int length = searchedText.Length - ( delimiterIndex + 1 );
            if( length > 0 )
            {
              this.AddSeparatedValuesToSearchedValues( searchedText.Substring( delimiterIndex + 1, length ), searchedValues );
            }
          }
        }
        else
        {
          // We have an odd index
          // It's there's another opening quote coming
          if( delimiterIndex != delimiterIndexes[ delimiterIndexes.Count - 1 ] )
          {
            // Take the string between these quote and add the separated values to the 
            // search values.
            int length = delimiterIndexes[ delimiterIndexIndex + 1 ] - ( delimiterIndex + 1 );
            this.AddSeparatedValuesToSearchedValues( searchedText.Substring( delimiterIndex + 1, length ), searchedValues );
          }
          else
          {
            // That's the last quote.
            // Add the separated parts of the resulting string in the search values
            int length = searchedText.Length - ( delimiterIndex + 1 );
            if( length > 0 )
            {
              this.AddSeparatedValuesToSearchedValues( searchedText.Substring( delimiterIndex + 1, length ), searchedValues );
            }
          }
        }
        delimiterIndexIndex++;
      }

      return searchedValues;
    }

    private void AddSeparatedValuesToSearchedValues( string stringToSeparate, List<string> searchedValues )
    {
      string[] splitedStringArray = null;

      splitedStringArray = stringToSeparate.Split( this.SearchValueDelimiters, StringSplitOptions.RemoveEmptyEntries );
      foreach( string searchValue in splitedStringArray )
      {
        if( ( !StringHelper.IsNullOrWhiteSpace( searchValue ) ) &&
            ( !string.IsNullOrEmpty( searchValue ) ) )
        {
          searchedValues.Add( searchValue.Trim() );
        }
      }
    }

    private void UpdateIsEnabled()
    {
#if !WINDOWS_PHONE
      if( !this.IsInitialized )
        return;
#endif

      FieldNameList filteredFieldNames = this.FilteredFieldNames;

      bool filteringEnabled = ( filteredFieldNames != null ) && ( filteredFieldNames.Count > 0 );

      this.IsEnabled = filteringEnabled;
    }

    private void UpdateFilterExpression()
    {
#if !WINDOWS_PHONE
      if( !this.IsInitialized )
        return;
#endif

      try
      {
        m_explicitlyUpdatingFilterExpression = true;
        this.FilterExpression = this.CreateFilterExpression();
      }
      finally
      {
        m_explicitlyUpdatingFilterExpression = false;
      }
    }

    private void UpdateFilterStates( bool useTransitions )
    {
      if( this.FilterExpression != m_lastLocallyCreatedFilterExpression )
      {
        VisualStateManager.GoToState( this, "TooComplex", useTransitions );
      }
      else if( ( m_cachedSearchedValues != null ) && ( m_cachedSearchedValues.Length > 0 ) )
      {
        VisualStateManager.GoToState( this, "HasFilter", useTransitions );
      }
      else
      {
        VisualStateManager.GoToState( this, "NoFilter", useTransitions );
      }
    }

    #endregion

    #region Private Fields

    private FilterExpressionBase m_lastLocallyCreatedFilterExpression;
    private bool m_clearingFilterExpression;
    private string[] m_cachedFilterFieldNames;
    private string[] m_cachedSearchedValues;
    private bool m_explicitlyUpdatingFilterExpression;

    #endregion

    #region IFilterExpressionProvider Members

    ICommand IFilterExpressionProvider.ClearFilterCommand
    {
      get
      {
        return this.ClearFilterCommand;
      }
    }

    FilterExpressionBase IFilterExpressionProvider.FilterExpression
    {
      get
      {
        return this.FilterExpression;
      }
      set
      {
        this.FilterExpression = value;
      }
    }

    FieldNameList IFilterExpressionProvider.FilteredFieldNames
    {
      get
      {
        return this.FilteredFieldNames;
      }
    }

    void IFilterExpressionProvider.ClearFilterExpression()
    {
      if( this.ClearFilterCanExecute( null ) )
        this.ClearFilterExcecute( null );
    }

    #endregion

    #region IWeakEventListener Implementation

    bool IWeakEventListener.ReceiveWeakEvent( Type managerType, object sender, EventArgs e )
    {
      bool handled = false;

      if( managerType == typeof( NotifyCollectionChangedManager ) )
      {
        NotifyCollectionChangedEventArgs collectionChangedEventArgs = ( NotifyCollectionChangedEventArgs )e;

        this.OnFilteredFieldNamesCollectionChanged( sender, collectionChangedEventArgs );

        handled = true;
      }

      return handled;
    }

    #endregion
  }
}
